package com.cerosoft.api.http;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRoute;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;

import com.cerosoft.api.exceptions.ConnectionException;
import com.cerosoft.api.http.handlers.AssetHandler;
import com.cerosoft.api.http.handlers.StringHandler;
import com.cerosoft.api.http.iface.IProgressCallback;
import com.cerosoft.api.http.iface.IProxy;
import com.cerosoft.api.http.models.Asset;
import com.cerosoft.api.http.models.HttpResult;

/**
 * HttpClient based HttpProxy. Implemented as singleton.
 * 
 * @author nliang@book.com
 * 
 */
public class HttpClientProxy implements IProxy {

	private static final String TAG = "HttpClientProxy";

	private static final int CONCURRENCY = 10;
	private static final int CONNECTION_TIMEOUT = 30000;
	private static final int SOCKET_TIMEOUT = 60000;

	private DefaultHttpClient client;

	private static HttpClientProxy instance;

	public static HttpClientProxy getInstance() {
		if (instance == null)
			instance = new HttpClientProxy();
		return instance;
	}

	public DefaultHttpClient getClient() {
		return client;
	}

	private HttpClientProxy() {
		// Initialize the thread safe http client - thread safe in this case
		HttpParams params = new BasicHttpParams();
		ConnPerRoute connPerRoute = new ConnPerRouteBean(CONCURRENCY);
		ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
		ConnManagerParams.setMaxTotalConnections(params, CONCURRENCY);
		HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
		HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);

		SchemeRegistry schemeRegistry = new SchemeRegistry();
		schemeRegistry.register(new Scheme("http", PlainSocketFactory
				.getSocketFactory(), 80));
		schemeRegistry.register(new Scheme("https", SSLSocketFactory
				.getSocketFactory(), 443));

		ClientConnectionManager connectionManager = new ThreadSafeClientConnManager(
				params, schemeRegistry);
		// this.client = new DefaultHttpClient(connectionManager, params);
		this.client = new DefaultHttpClient();

		// Enable gzip acceptance
		this.client.addRequestInterceptor(new HttpRequestInterceptor() {
			public void process(final HttpRequest request,
					final HttpContext context) throws HttpException,
					IOException {
				if (!request.containsHeader("Accept-Encoding"))
					request.addHeader("Accept-Encoding", "gzip");
			}
		});

		// Do not automatically follow redirects
		this.client.getParams().setBooleanParameter(
				ClientPNames.HANDLE_REDIRECTS, false);
	}

	public HttpResult<String> get(String url, Map<String, String> parameters,
			boolean needEncode) throws ConnectionException {
		String totalURL = HttpClientHelper.queryString(url, parameters,
				needEncode);
		HttpGet get = new HttpGet(totalURL);
		RequestExecutor<String> request = new RequestExecutor<String>(get,
				new StringHandler());
		return request.execute();
	}

	public HttpResult<String> post(String url, Map<String, String> parameters)
			throws ConnectionException {
		HttpPost post = new HttpPost(url);
		post.setEntity(HttpClientHelper.encodedFormEntity(parameters));
		RequestExecutor<String> request = new RequestExecutor<String>(post,
				new StringHandler());
		return request.execute();
	}

	public HttpResult<String> syncML(String url, String message)
			throws ConnectionException {
		HttpPost post = new HttpPost(url);

		// SyncML specific Http headers
		post.setHeader("Content-Type", "application/vnd.syncml+xml");
		post.setHeader("Connection", "Keep-Alive");

		post.setEntity(new ByteArrayEntity(message.getBytes()));
		RequestExecutor<String> request = new RequestExecutor<String>(post,
				new StringHandler());
		return request.execute();
	}

	public HttpResult<Asset> download(String url,
			Map<String, String> parameters, IProgressCallback callback)
			throws ConnectionException {
		return download(url, parameters, -1, -1, callback);
	}

	public HttpResult<Asset> download(String url,
			Map<String, String> parameters, long startByte, long endByte,
			IProgressCallback callback) throws ConnectionException {
		HttpGet get = new HttpGet(HttpClientHelper.queryString(url, parameters));
		if (endByte >= 0 & startByte >= 0 && startByte <= endByte)
			get.addHeader("range", "bytes=" + startByte + "-" + endByte);

		return new RequestExecutor<Asset>(get, new AssetHandler(callback))
				.execute();
	}

	public HttpResult<Asset> download(String url,
			Map<String, String> parameters, long startByte, long endByte,
			OutputStream out, IProgressCallback callback)
			throws ConnectionException {
		return download(url, parameters, null, startByte, endByte, out,
				callback);
		// HttpGet get = new HttpGet(queryString(url, parameters));
		// if (endByte >= 0 & startByte >= 0 && startByte <= endByte)
		// get.addHeader("range", "bytes=" + startByte + "-" + endByte);
		// return new RequestExecutor<Asset>(get, new AssetHandler(out,
		// callback)).execute();
	}

	public HttpResult<Asset> download(String url,
			Map<String, String> parameters, Map<String, String> headers,
			long startByte, long endByte, OutputStream out,
			IProgressCallback callback) throws ConnectionException {
		HttpGet get = new HttpGet(HttpClientHelper.queryString(url, parameters));
		if (endByte >= 0 & startByte >= 0 && startByte <= endByte)
			get.addHeader("range", "bytes=" + startByte + "-" + endByte);
		if (headers != null) {
			for (String key : headers.keySet()) {
				get.addHeader(key, headers.get(key));
			}
		}
		return new RequestExecutor<Asset>(get, new AssetHandler(out, callback))
				.execute();
	}

	// Get a cookie from the cookie store
	public String getCookie(String key) {
		String value = null;
		CookieStore cookieStore = this.client.getCookieStore();
		List<Cookie> cookies = cookieStore.getCookies();
		for (Cookie cookie : cookies) {
			if (cookie.getName().equalsIgnoreCase(key)) {
				value = cookie.getValue();
				break;
			}
		}
		return value;
	}

	// Set a cookie
	public void setCookie(String key, String value, String domain, String path,
			Date expiryDate) {
		CookieStore cookieStore = this.client.getCookieStore();
		BasicClientCookie cookie = new BasicClientCookie(key, value);
		if (domain != null)
			cookie.setDomain(domain);
		if (path != null)
			cookie.setPath(path);
		if (expiryDate != null)
			cookie.setExpiryDate(expiryDate);
		cookieStore.addCookie(cookie);
	}

	// Clear cookies
	public void clearCookies() {
		client.getCookieStore().clear();
	}

	public List<Cookie> getCookies() {
		return client.getCookieStore().getCookies();
	}

}